/*
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 * openUBMC is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *         http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

#include <gtest/gtest.h>
#include <ipmb/ipmb.h>
#include <mc/dict.h>
#include <mc/engine.h>
#include <mc/engine/service.h>
#include <mc/reflect/reflect.h>
#include <mc/variant.h>
#include <memory>

class ipmb_interface_test : public ::testing::Test {
protected:
    void SetUp() override {
        m_service = std::make_unique<mc::engine::service>("bmc.kepler.test_ipmb");
        m_service->init();
        m_service->start();

        m_ipmb = std::make_unique<mc::ipmb>(m_service.get());
    }

    void TearDown() override {
        m_ipmb.reset();
        m_service->stop();
        m_service.reset();
    }

    std::unique_ptr<mc::engine::service> m_service;
    std::unique_ptr<mc::ipmb>            m_ipmb;
};

TEST_F(ipmb_interface_test, test_constructor) {
    ASSERT_NE(m_ipmb, nullptr);
    EXPECT_THROW({ mc::ipmb invalid_ipmb(nullptr); }, mc::invalid_argument_exception);
}

TEST_F(ipmb_interface_test, test_configuration) {
    m_ipmb->set_retry_count(5);
    EXPECT_EQ(m_ipmb->get_retry_count(), 5);

    m_ipmb->set_retry_count(-1);
    EXPECT_EQ(m_ipmb->get_retry_count(), 0);
}

TEST_F(ipmb_interface_test, test_request_validation) {
    mc::ipmi_request request{
        .net_fn = mc::ipmi_net_fn::app, .cmd = 0x01, .data = {0x10, 0x20}, .timeout_ms = 100};

    EXPECT_EQ(request.net_fn, mc::ipmi_net_fn::app);
    EXPECT_EQ(request.cmd, 0x01);
    EXPECT_EQ(request.data, std::vector<uint8_t>({0x10, 0x20}));
    EXPECT_EQ(request.timeout_ms, 100);
}

class ipmb_command_test : public ::testing::Test {
protected:
    void SetUp() override {
        m_service = std::make_unique<mc::engine::service>("bmc.kepler.test_ipmb");
        m_service->init();
        m_service->start();

        m_ipmb = std::make_unique<mc::ipmb>(m_service.get());
    }

    void TearDown() override {
        m_ipmb.reset();
        m_service->stop();
        m_service.reset();
    }

    std::unique_ptr<mc::engine::service> m_service;
    std::unique_ptr<mc::ipmb>            m_ipmb;
};

TEST_F(ipmb_command_test, test_command_structure) {
    mc::ipmi_request request{.net_fn     = mc::ipmi_net_fn::sensor,
                             .cmd        = 0x2D,
                             .data       = {0x01, 0x02, 0x03},
                             .timeout_ms = 100};

    EXPECT_EQ(request.net_fn, mc::ipmi_net_fn::sensor);
    EXPECT_EQ(request.cmd, 0x2D);
    EXPECT_EQ(request.data, std::vector<uint8_t>({0x01, 0x02, 0x03}));
    EXPECT_EQ(request.timeout_ms, 100);
}

TEST_F(ipmb_command_test, test_network_function_codes) {
    std::vector<mc::ipmi_net_fn> net_fns = {
        mc::ipmi_net_fn::chassis,   mc::ipmi_net_fn::bridge,    mc::ipmi_net_fn::sensor,
        mc::ipmi_net_fn::app,       mc::ipmi_net_fn::firmware,  mc::ipmi_net_fn::storage,
        mc::ipmi_net_fn::transport, mc::ipmi_net_fn::group_ext, mc::ipmi_net_fn::oem};

    for (auto net_fn : net_fns) {
        mc::ipmi_request request{.net_fn = net_fn, .cmd = 0x01, .data = {}, .timeout_ms = 100};

        EXPECT_EQ(request.net_fn, net_fn);

        EXPECT_EQ(request.net_fn, net_fn);
        EXPECT_EQ(request.cmd, 0x01);
        EXPECT_EQ(request.data, std::vector<uint8_t>{});
        EXPECT_EQ(request.timeout_ms, 100);
    }
}

TEST_F(ipmb_command_test, test_channel_types) {
    std::vector<mc::ipmb_channel_type> channels = {mc::ipmb_channel_type::me,
                                                   mc::ipmb_channel_type::smm,
                                                   mc::ipmb_channel_type::mmc};

    for (auto channel : channels) {
        mc::ipmi_request request{
            .net_fn = mc::ipmi_net_fn::app, .cmd = 0x01, .data = {}, .timeout_ms = 100};

        EXPECT_EQ(request.net_fn, mc::ipmi_net_fn::app);
        EXPECT_EQ(request.cmd, 0x01);
        EXPECT_EQ(request.data, std::vector<uint8_t>{});
        EXPECT_EQ(request.timeout_ms, 100);
    }
}

TEST_F(ipmb_command_test, test_response_structure) {
    mc::ipmi_response response;
    response.completion_code = mc::ipmi_completion_code::success;
    response.data            = {0x01, 0x02, 0x03};

    EXPECT_EQ(response.completion_code, mc::ipmi_completion_code::success);
    EXPECT_EQ(response.data, std::vector<uint8_t>({0x01, 0x02, 0x03}));
}

TEST_F(ipmb_command_test, test_completion_codes) {
    std::vector<mc::ipmi_completion_code> completion_codes = {
        mc::ipmi_completion_code::success, mc::ipmi_completion_code::node_busy,
        mc::ipmi_completion_code::invalid_command, mc::ipmi_completion_code::timeout,
        mc::ipmi_completion_code::unknown_error};

    for (auto cc : completion_codes) {
        mc::ipmi_response response;
        response.completion_code = cc;
        response.data            = {};

        EXPECT_EQ(response.completion_code, cc);
    }
}

class ipmi_utils_test : public ::testing::Test {};

class parse_response_test : public ::testing::Test {
protected:
    void SetUp() override {
        m_service = std::make_unique<mc::engine::service>("bmc.kepler.test_parse_response");
        m_service->init();
        m_service->start();

        m_ipmb = std::make_unique<mc::ipmb>(m_service.get());
    }

    void TearDown() override {
        m_ipmb.reset();
        m_service->stop();
        m_service.reset();
    }

    std::unique_ptr<mc::engine::service> m_service;
    std::unique_ptr<mc::ipmb>            m_ipmb;
};

TEST_F(parse_response_test, test_parse_response_boundaries) {
    std::vector<uint8_t> max_data(255, 0xAA);
    mc::variant          completion_code = static_cast<int64_t>(0x00);
    mc::variant          response_data   = max_data;

    auto result = m_ipmb->parse_response(completion_code, response_data);

    EXPECT_TRUE(result.has_value());
    if (result.has_value()) {
        EXPECT_EQ(result->completion_code, mc::ipmi_completion_code::success);
        EXPECT_EQ(result->data.size(), 255);
        EXPECT_EQ(result->data[0], 0xAA);
        EXPECT_EQ(result->data[254], 0xAA);
    }

    mc::variant empty_data   = std::vector<uint8_t>{};
    auto        empty_result = m_ipmb->parse_response(completion_code, empty_data);

    EXPECT_TRUE(empty_result.has_value());
    if (empty_result.has_value()) {
        EXPECT_EQ(empty_result->completion_code, mc::ipmi_completion_code::success);
        EXPECT_TRUE(empty_result->data.empty());
    }
}

TEST_F(parse_response_test, test_parse_response_completion_codes) {
    mc::variant response_data = std::vector<uint8_t>{0x01, 0x02};

    std::vector<std::pair<int64_t, mc::ipmi_completion_code>> completion_codes = {
        {0x00, mc::ipmi_completion_code::success},
        {0xC0, mc::ipmi_completion_code::node_busy},
        {0xC1, mc::ipmi_completion_code::invalid_command},
        {0xC3, mc::ipmi_completion_code::timeout},
        {0xFF, mc::ipmi_completion_code::unknown_error}};

    for (const auto& [code_value, expected_code] : completion_codes) {
        mc::variant completion_code = code_value;
        auto        result          = m_ipmb->parse_response(completion_code, response_data);

        EXPECT_TRUE(result.has_value());
        if (result.has_value()) {
            EXPECT_EQ(result->completion_code, expected_code);
            EXPECT_EQ(result->data.size(), 2);
        }
    }
}

TEST_F(parse_response_test, test_parse_response_type_validation) {
    mc::variant response_data = std::vector<uint8_t>{0x01};

    mc::variant uint8_completion_code = static_cast<uint8_t>(0x00);
    auto        uint8_result = m_ipmb->parse_response(uint8_completion_code, response_data);
    EXPECT_TRUE(uint8_result.has_value());

    mc::variant int32_completion_code = static_cast<int32_t>(0x00);
    auto        int32_result = m_ipmb->parse_response(int32_completion_code, response_data);
    EXPECT_TRUE(int32_result.has_value());

    mc::variant valid_completion_code = static_cast<int64_t>(0x00);

    mc::variant empty_variant;
    auto        empty_variant_result = m_ipmb->parse_response(valid_completion_code, empty_variant);
    EXPECT_FALSE(empty_variant_result.has_value());

    mc::variant bool_data   = true;
    auto        bool_result = m_ipmb->parse_response(valid_completion_code, bool_data);
    EXPECT_FALSE(bool_result.has_value());
}

TEST_F(ipmi_utils_test, test_format_completion_code) {
    EXPECT_EQ(mc::ipmi_utils::format_completion_code(mc::ipmi_completion_code::success), "Success");
    EXPECT_EQ(mc::ipmi_utils::format_completion_code(mc::ipmi_completion_code::node_busy),
              "Node Busy");
    EXPECT_EQ(mc::ipmi_utils::format_completion_code(mc::ipmi_completion_code::invalid_command),
              "Invalid Command");
    EXPECT_EQ(mc::ipmi_utils::format_completion_code(mc::ipmi_completion_code::timeout), "Timeout");
    EXPECT_EQ(mc::ipmi_utils::format_completion_code(mc::ipmi_completion_code::unknown_error),
              "Unknown Error");

    auto unknown_code = static_cast<mc::ipmi_completion_code>(0xAB);
    EXPECT_EQ(mc::ipmi_utils::format_completion_code(unknown_code), "Unknown Completion Code");

    EXPECT_EQ(static_cast<uint8_t>(mc::ipmi_completion_code::success), 0x00);
    EXPECT_EQ(static_cast<uint8_t>(mc::ipmi_completion_code::node_busy), 0xC0);
}

TEST_F(ipmi_utils_test, test_is_success) {
    EXPECT_TRUE(mc::ipmi_utils::is_success(mc::ipmi_completion_code::success));
    EXPECT_FALSE(mc::ipmi_utils::is_success(mc::ipmi_completion_code::node_busy));
    EXPECT_FALSE(mc::ipmi_utils::is_success(mc::ipmi_completion_code::invalid_command));
    EXPECT_FALSE(mc::ipmi_utils::is_success(mc::ipmi_completion_code::timeout));
}

TEST_F(ipmi_utils_test, test_is_retryable) {
    EXPECT_TRUE(mc::ipmi_utils::is_retryable(mc::ipmi_completion_code::node_busy));
    EXPECT_TRUE(mc::ipmi_utils::is_retryable(mc::ipmi_completion_code::timeout));
    EXPECT_FALSE(mc::ipmi_utils::is_retryable(mc::ipmi_completion_code::success));
    EXPECT_FALSE(mc::ipmi_utils::is_retryable(mc::ipmi_completion_code::invalid_command));
}

TEST_F(ipmi_utils_test, test_basic_structures) {
    mc::ipmb_channel_type channel = mc::ipmb_channel_type::smm;
    EXPECT_EQ(static_cast<uint8_t>(channel), 0x00);

    channel = mc::ipmb_channel_type::me;
    EXPECT_EQ(static_cast<uint8_t>(channel), 0x01);

    channel = mc::ipmb_channel_type::mmc;
    EXPECT_EQ(static_cast<uint8_t>(channel), 0x02);

    mc::ipmi_net_fn net_fn = mc::ipmi_net_fn::chassis;
    EXPECT_EQ(static_cast<uint8_t>(net_fn), 0x00);

    net_fn = mc::ipmi_net_fn::app;
    EXPECT_EQ(static_cast<uint8_t>(net_fn), 0x06);
}

TEST_F(ipmb_interface_test, test_retry_mechanism) {
    m_ipmb->set_retry_count(2);
    EXPECT_EQ(m_ipmb->get_retry_count(), 2);

    m_ipmb->set_retry_count(3);
    EXPECT_EQ(m_ipmb->get_retry_count(), 3);
}

TEST_F(ipmb_interface_test, test_ipmi_request_interface) {
    mc::ipmi_request request{
        .net_fn = mc::ipmi_net_fn::app, .cmd = 0x01, .data = {}, .timeout_ms = 100};
    EXPECT_NO_THROW({ m_ipmb->send_request(mc::ipmb_channel_type::me, request); });

    EXPECT_NO_THROW({
        m_ipmb->send_request(mc::ipmb_channel_type::me, mc::ipmi_net_fn::sensor, 0x2D, {0x01},
                             5000);
    });
}

TEST_F(ipmb_interface_test, test_error_handling) {
    mc::ipmi_request invalid_request{
        .net_fn = mc::ipmi_net_fn::app, .cmd = 0x01, .data = {}, .timeout_ms = 0};

    EXPECT_EQ(invalid_request.timeout_ms, 0);

    mc::ipmi_request empty_data_request{
        .net_fn = mc::ipmi_net_fn::app, .cmd = 0x01, .data = {}, .timeout_ms = 100};
    EXPECT_TRUE(empty_data_request.data.empty());
}

TEST_F(ipmb_command_test, test_response_parsing) {
    mc::variant completion_code = static_cast<int64_t>(0x00);
    mc::variant response_data   = std::vector<uint8_t>{0x01, 0x02, 0x03, 0x04};

    auto result = m_ipmb->parse_response(completion_code, response_data);

    EXPECT_TRUE(result.has_value());
    if (result.has_value()) {
        EXPECT_EQ(result->completion_code, mc::ipmi_completion_code::success);
        EXPECT_EQ(result->data.size(), 4);
        EXPECT_EQ(result->data[0], 0x01);
        EXPECT_EQ(result->data[3], 0x04);
    }

    mc::variant error_completion_code = static_cast<int64_t>(0xC0);
    mc::variant empty_data            = std::vector<uint8_t>{};

    auto error_result = m_ipmb->parse_response(error_completion_code, empty_data);

    EXPECT_TRUE(error_result.has_value());
    if (error_result.has_value()) {
        EXPECT_EQ(error_result->completion_code, mc::ipmi_completion_code::node_busy);
        EXPECT_TRUE(error_result->data.empty());
    }

    mc::variant invalid_completion_code = std::string("invalid");
    mc::variant valid_data              = std::vector<uint8_t>{0x01, 0x02};

    auto invalid_result = m_ipmb->parse_response(invalid_completion_code, valid_data);

    EXPECT_FALSE(invalid_result.has_value());

    mc::variant valid_completion_code = static_cast<int64_t>(0x00);
    mc::variant string_data = std::string("test");

    auto string_data_result = m_ipmb->parse_response(valid_completion_code, string_data);

    EXPECT_TRUE(string_data_result.has_value());
    if (string_data_result.has_value()) {
        EXPECT_EQ(string_data_result->completion_code, mc::ipmi_completion_code::success);
        EXPECT_EQ(string_data_result->data.size(), 4);
        EXPECT_EQ(string_data_result->data[0], 't');
        EXPECT_EQ(string_data_result->data[3], 't');
    }
}

TEST_F(ipmb_command_test, test_data_type_conversion) {
    mc::ipmi_request empty_request{
        .net_fn = mc::ipmi_net_fn::app, .cmd = 0x01, .data = {}, .timeout_ms = 100};
    EXPECT_TRUE(empty_request.data.empty());

    mc::ipmi_request single_byte_request{
        .net_fn = mc::ipmi_net_fn::app, .cmd = 0x01, .data = {0xFF}, .timeout_ms = 100};
    EXPECT_EQ(single_byte_request.data.size(), 1);
    EXPECT_EQ(single_byte_request.data[0], 0xFF);

    mc::ipmi_request multi_byte_request{.net_fn     = mc::ipmi_net_fn::app,
                                        .cmd        = 0x01,
                                        .data       = {0x01, 0x02, 0x03, 0x04, 0x05},
                                        .timeout_ms = 100};
    EXPECT_EQ(multi_byte_request.data.size(), 5);
    EXPECT_EQ(multi_byte_request.data[0], 0x01);
    EXPECT_EQ(multi_byte_request.data[4], 0x05);
}

TEST_F(ipmb_command_test, test_network_driver_compatibility) {
    mc::ipmi_response standard_response;
    standard_response.completion_code = mc::ipmi_completion_code::success;
    standard_response.data            = {0x00, 0x01, 0x02, 0x03};

    EXPECT_EQ(standard_response.completion_code, mc::ipmi_completion_code::success);
    EXPECT_EQ(standard_response.data.size(), 4);

    std::vector<uint8_t> max_data(255, 0xFF);
    mc::ipmi_response    max_response;
    max_response.completion_code = mc::ipmi_completion_code::success;
    max_response.data            = max_data;

    EXPECT_EQ(max_response.data.size(), 255);
    EXPECT_EQ(max_response.data[0], 0xFF);
    EXPECT_EQ(max_response.data[254], 0xFF);
}

TEST_F(ipmb_command_test, test_performance_boundaries) {
    mc::ipmi_request min_timeout_request{
        .net_fn = mc::ipmi_net_fn::app, .cmd = 0x01, .data = {}, .timeout_ms = 1};
    EXPECT_EQ(min_timeout_request.timeout_ms, 1);

    mc::ipmi_request max_timeout_request{
        .net_fn = mc::ipmi_net_fn::app, .cmd = 0x01, .data = {}, .timeout_ms = 1000};
    EXPECT_EQ(max_timeout_request.timeout_ms, 1000);

    mc::ipmi_request min_cmd_request{
        .net_fn = mc::ipmi_net_fn::app, .cmd = 0x00, .data = {}, .timeout_ms = 100};
    EXPECT_EQ(min_cmd_request.cmd, 0x00);

    mc::ipmi_request max_cmd_request{
        .net_fn = mc::ipmi_net_fn::app, .cmd = 0xFF, .data = {}, .timeout_ms = 100};
    EXPECT_EQ(max_cmd_request.cmd, 0xFF);
}

TEST_F(ipmb_command_test, test_protocol_compatibility) {
    mc::ipmi_request get_device_id_request{
        .net_fn = mc::ipmi_net_fn::app, .cmd = 0x01, .data = {}, .timeout_ms = 100};
    EXPECT_EQ(get_device_id_request.net_fn, mc::ipmi_net_fn::app);
    EXPECT_EQ(get_device_id_request.cmd, 0x01);

    mc::ipmi_request get_sensor_reading_request{.net_fn     = mc::ipmi_net_fn::sensor,
                                                .cmd        = 0x2D,
                                                .data       = {0x01},
                                                .timeout_ms = 100};
    EXPECT_EQ(get_sensor_reading_request.net_fn, mc::ipmi_net_fn::sensor);
    EXPECT_EQ(get_sensor_reading_request.cmd, 0x2D);
    EXPECT_EQ(get_sensor_reading_request.data.size(), 1);
    EXPECT_EQ(get_sensor_reading_request.data[0], 0x01);

    mc::ipmi_request oem_request{.net_fn     = mc::ipmi_net_fn::oem,
                                 .cmd        = 0x44,
                                 .data       = {0x01, 0x02, 0x03},
                                 .timeout_ms = 100};
    EXPECT_EQ(oem_request.net_fn, mc::ipmi_net_fn::oem);
    EXPECT_EQ(oem_request.cmd, 0x44);
    EXPECT_EQ(oem_request.data.size(), 3);
}

TEST_F(ipmb_interface_test, test_resource_management) {
    auto service1 = std::make_unique<mc::engine::service>("test_service_1");
    auto service2 = std::make_unique<mc::engine::service>("test_service_2");

    auto ipmb1 = std::make_unique<mc::ipmb>(service1.get());
    auto ipmb2 = std::make_unique<mc::ipmb>(service2.get());

    EXPECT_NE(ipmb1, nullptr);
    EXPECT_NE(ipmb2, nullptr);
    EXPECT_NE(ipmb1.get(), ipmb2.get());

    ipmb1->set_retry_count(1);
    ipmb2->set_retry_count(2);

    EXPECT_EQ(ipmb1->get_retry_count(), 1);
    EXPECT_EQ(ipmb2->get_retry_count(), 2);

    ipmb1.reset();
    ipmb2.reset();
    service1.reset();
    service2.reset();
}
